﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Web.UI;
using NeuralNetwork.Generic;
using NeuralNetwork.Neurons;
using NeuralNetwork.Layers;
using NeuralNetwork.OutputTypes;
using NeuralNetwork.Normalization; 

namespace NeuralNetwork
{
    public class Network
    {
        // Common values
        public Random rand = new Random();
        public Log log = new Log();
        public DateTime created = DateTime.Now;
        public Activation.Function activation = new Activation.Sigmoid();

        // Layers
        static Factory<Layer> layerFactory = new Factory<Layer>();
        public InputLayer input = null;
        public List<Layer> layers = new List<Layer>();
        public OutputLayer output = null;

        // Network data
        private double recognitionLevel = 0.95;
        private double baseActivationThreshold = 0.98;
        private double prunningLevel = 0.02;
        private int neuronRestTime = 10;
        private int observeTime = 1;
        private string inputNeurontype = "Neuron";
        private string hiddenNeurontype = "Neuron";
        private string outputNeurontype = "OutputNeuron";

        public bool SelfOrganizingMap(int nofInputs,
                                      List<Output> outputs,
                                      Activation.Function activation,
                                      NormalizeFunction normalize)
        {
            log.Print("Feedforward Network");
            layers.Clear();

            inputNeurontype = "InputNeuron";
            hiddenNeurontype = "";
            outputNeurontype = "OutputNeuron";
            this.observeTime = 1;

            // Make layers
            layers.Add(new InputLayer(this, nofInputs, inputNeurontype));
            layers.Add(new OutputLayer(this, outputs, outputNeurontype));

            // Connect them            
            for (int i = 0; i < layers.Count - 1; i++)
                layers[i].Connect(layers[i + 1]);

            // Set normalization
            input.Normalize = normalize;
            this.activation = activation;

            return true;
        }

        public bool FeedForward(int nofInputs, 
                                int nofHiddenLayers, 
                                int nofHiddenNeuronsPerLayer,                                 
                                List<Output> outputs,
                                Activation.Function activation,
                                NormalizeFunction norm)
        {
            log.Print("Feedforward Network");
            layers.Clear();

            inputNeurontype = "InputNeuron";
            hiddenNeurontype = "Neuron";
            outputNeurontype = "OutputNeuron";
            this.observeTime = 1;

            // Make New layers
            // - input
            layers.Add(new InputLayer(this, nofInputs, inputNeurontype));
            // - hidden
            for(int i = 0; i < nofHiddenLayers; i++) 
            {
                layers.Add(new HiddenLayer(this, nofHiddenNeuronsPerLayer, hiddenNeurontype));
            }
            // - output
            layers.Add(new OutputLayer(this, outputs, outputNeurontype));                

            // Connect them            
            for (int i = 0; i < layers.Count - 1; i++)
                layers[i].Connect(layers[i + 1]);
            
            // Set normalize function
            input.Normalize = norm;
            this.activation = activation;

            return true;
        }

        virtual public void AddIDToNode(XmlDocument doc, XmlNode node, string id)
        {
            XmlAttribute attr = doc.CreateAttribute("id");
            attr.Value = id;
            node.Attributes.Append(attr);
        }

        virtual public XmlNode AddChildToNode(XmlDocument doc, XmlNode node, string name, object value)
        {
            XmlElement elem = doc.CreateElement(name);
            if (value != null) elem.InnerText = value.ToString();
            node.AppendChild(elem);
            return elem;
        }

        public string GetUniqueID()
        {
            return Guid.NewGuid().ToString();
        }

        public void Reset()
        {
            foreach (Layer layer in layers) layer.Reset();
        }

        public void Update()
        {
            foreach (Layer layer in layers) layer.Update();
        }

        public void Revert()
        {
            foreach (Layer layer in layers) layer.Revert();
        }

        public void Prune()
        {
            foreach (Layer layer in layers) layer.Prune();
        }

        public int ObserveTime
        {
            get { return observeTime; }
        }

        public double RecognitionLevel
        {
            get{ return recognitionLevel; }
        }
        
        public double PrunningLevel
        {
            get { return prunningLevel; }
        }

        public int NeuronRestTime
        {
            get { return neuronRestTime; }
        }
        
        public double Noise
        {
            get { return 1 + (rand.NextDouble() - 0.5)/100.0; }
        }

        public double ActivationThreshold
        {
            get { return baseActivationThreshold; }// +(rand.NextDouble() - 0.5) / 10.0; }
        }        

        public List<Output> OutputValues
        {
            get
            {
                List<Output> rc = new List<Output>();
                foreach (OutputNeuron on in output.neurons) rc.Add(on.Output);
                return rc;
            }
        }

        public void Save(string filename)
        {
            XmlDocument doc = new XmlDocument();
            XmlNode root = doc.CreateElement("network");

            AddChildToNode(doc, root, "recognitionLevel", recognitionLevel);
            AddChildToNode(doc, root, "baseActivationThreshold", baseActivationThreshold);
            AddChildToNode(doc, root, "prunningLevel", prunningLevel);
            AddChildToNode(doc, root, "neuronRestTime", neuronRestTime);
            AddChildToNode(doc, root, "inputNeurontype", inputNeurontype);
            AddChildToNode(doc, root, "hiddenNeurontype", hiddenNeurontype);
            AddChildToNode(doc, root, "outputNeurontype", outputNeurontype);
            AddChildToNode(doc, root, "observeTime", observeTime);

            XmlNode outputNodes = AddChildToNode(doc, root, "outputs", null);
            foreach (OutputNeuron on in output.neurons)
                AddChildToNode(doc, outputNodes, "output", on.Value.ToString() );

            XmlNode layerNodes = AddChildToNode(doc, root, "layers", null);
            foreach (Layer l in layers) l.Save(doc, layerNodes);

            XmlNode axonNodes = AddChildToNode(doc, root, "axons", null);
            foreach (Layer l in layers) 
            {
                foreach (Neuron n in l.neurons)
                    foreach (Axon a in n.axons)
                        a.Save(doc, axonNodes);
            }

            doc.AppendChild(root);
            doc.Save(filename);            
        }

        virtual public void Load(string filename)
        {
            layers.Clear();

            // Default Values
            recognitionLevel = 0.95;
            baseActivationThreshold = 1;
            prunningLevel = 0.10;
            neuronRestTime = 10;
            inputNeurontype = "InputNeuron";
            hiddenNeurontype = "Neuron";
            outputNeurontype = "OutputNeuron";
            observeTime = 0;

            // Load Network description (xml)
            XmlDocument doc = new XmlDocument();
            doc.Load(filename);

            // Get network data
            XmlNodeList xnl = doc.GetElementsByTagName("network");
            if (xnl.Count != 1) return;
            XmlNode networkNode = xnl[0];
            foreach (XmlNode n in networkNode.ChildNodes)
            {
                switch (n.Name.ToUpper())
                {
                    case "OBSERVETIME":
                        observeTime = Convert.ToInt32(n.InnerText);
                        break;
                    case "RECOGNITIONLEVEL":
                        recognitionLevel = Convert.ToDouble(n.InnerText);
                        break;
                    case "BASEACTIVATIONTHRESHOLD":
                        baseActivationThreshold = Convert.ToDouble(n.InnerText);
                        break;
                    case "PRUNNINGLEVEL":
                        prunningLevel = Convert.ToDouble(n.InnerText);
                        break;
                    case "NEURONRESTTIME":
                        neuronRestTime = Convert.ToInt32(n.InnerText);
                        break;
                    case "INPUTNEURONTYPE":
                        inputNeurontype = n.InnerText.Trim();
                        break;
                    case "HIDDENNEURONTYPE":
                        hiddenNeurontype = n.InnerText.Trim();
                        break;
                    case "OUTPUTNEURONTYPE":
                        outputNeurontype = n.InnerText.Trim();
                        break;
                    default:
                        //ignore
                        break;
                }
            }

            // Generate Layers
            Layer layer = null;                   
            List<Pair> NodeLayerPairs = new List<Pair>();
            foreach (XmlNode layerNodes in networkNode.ChildNodes)
            {
                switch (layerNodes.Name.ToUpper())
                {
                    case "LAYERS":
                        NodeLayerPairs.Clear();
                        foreach (XmlNode layerNode in layerNodes.ChildNodes)
                        {
                            layer = layerFactory.Make(layerNode.Name.ToUpper().Trim(), this);
                            if (layer != null)
                            {
                                foreach (XmlAttribute attr in layerNode.Attributes)
                                    if (attr.Name.ToUpper() == "ID") layer.ID = attr.Value.Trim();
                                layers.Add(layer);
                                NodeLayerPairs.Add(new Pair(layer, layerNode));
                            }
                        }
                        foreach (Pair p in NodeLayerPairs) ((Layer)(p.First)).Load((XmlNode)(p.Second));
                        break;

                    default:
                        //ignore
                        break;
                }
            }

            // Create Nuerons and connect Neurons 
            foreach (XmlNode axonNodes in networkNode.ChildNodes)
            {
                switch (axonNodes.Name.ToUpper())
                {
                    case "AXONS":
                        foreach (XmlNode axonNode in axonNodes.ChildNodes)
                        {
                            Axon axon = new Axon();
                            axon.Load(this, axonNode);
                        }
                        break;

                    default:
                        //ignore
                        break;
                }
            }            
        }
    }
}
